/* tripl/arch/interp/math.c 
 * 
 * This file is part of tripl. 
 * 
 * tripl is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * tripl is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with tripl. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>

#include <klist.h>
#include <tripl/tripl.h>
#include <tripl/limits.h>
#include <tripl/arch.h>



extern char *psave;
extern struct klist_node *find_name(char *);

__arch_templ static long getnum(char **p, int prio);


#define PRIO_MAX 	9
#define PRIO_MIN 	0


__arch_templ long interp_math_expr(char *p, char *var) {
	struct klist_node *n;

	p++;
	_skip_spaces(p);

	n = find_name(var);

	if (!n) {
		error(var);

		exit(generic_error("name not found", psave, lno));
	}

	if (n->xmask & XMASK_WEAK) 
		exit(generic_error("trying to modify weak variable", psave, lno));

	if (n->xmask & 4) 
		exit(generic_error("variable sets to be read-only", psave, lno));


	n->value = getnum(&p, PRIO_MAX);

	return n->value;
}


/* algorithm copied from os in a month (write an os in 30 days), kawai */

__arch_templ static long getnum(char **pp, int prio) {
	struct klist_node *n;
	int type, out;
	char *p = *pp, *var;

	long a = 0, b = 0;

	/* a + b, first we read a by ourselves. 
	 * and, okay, we cannot use getword here because... */

	switch (*p) {
	case '+':
	case '-':

		/* +123 */

		getword(p, &type, (int *) &a);
		_skip_word(p);

		break;


	case '(':
		p++;
		_skip_spaces(p);

		a = getnum(&p, PRIO_MAX);

		if (*p != ')') 
			exit(generic_error("no ')' found", psave, lno));

		break;

	default:
		if (isnr(*p)) 
			getword(p, &type, (int *) &a);

		else if (ischr(*p)) {
			getword(p, &type, &out);

			var = strndup(p, out);

			n = find_name(var);

			if (!n) {
				error(var);

				exit(generic_error("name not found", psave, lno));
			}

			if (n->xmask & XMASK_WEAK) 
				exit(generic_error("trying to modify weak variable", psave, lno));

			a = n->value;

			free(var);
		}

		else 
			exit(generic_error("invalid unary operator before number", psave, lno));

		_skip_word(p);

		break;
	}

	for (;;) {
		switch (*p) {
		case '+':
			if (prio <= 2) 
				goto fin;

			p++;
			_skip_spaces(p);

			b = getnum(&p, 2);
			a += b;

			break;

		case '-':
			if (prio <= 2) 
				goto fin;

			p++;
			_skip_spaces(p);

			b = getnum(&p, 2);
			a -= b;

			break;

		case '*':
			if (prio <= 1) 
				goto fin;

			p++;
			_skip_spaces(p);

			b = getnum(&p, 1);

			a *= b;

			break;

		case '/':
			if (prio <= 1) 
				goto fin;

			p++;
			_skip_spaces(p);

			b = getnum(&p, 1);

			a /= b;

			break;

		default:
			goto fin;
		}
	}

fin:	_skip_spaces(p);
	*pp = p;

	return a;
}



